/+
module:	bufferedstream.d
author: Wei Li (oldrev@gmail.com) (D Version)
		and below
+/

// This file is a port from Mono corlib by Wei Li

// System.IO.BufferedStream
//
// Author:
//   Matt Kimball (matt@kimball.net)
//   Ville Palo <vi64pa@kolumbus.fi>
//
// Copyright (C) 2004 Novell (http://www.novell.com)
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

module dotmars.io.bufferedstream;


public import dotmars.io.stream;

public import dotmars.io.ioexcept;

final class BufferedStream : Stream 
{
	private Stream m_baseStream = null;
	private ubyte[] m_buffer;
	private size_t m_buffer_pos;
	private size_t m_buffer_read_ahead;
	private bool m_buffer_reading;
	private bool m_closed = false;

	public alias Stream.read read;
	public alias Stream.write write;

	invariant {
		assert(m_baseStream !is null);
	}

	public this(Stream stream, size_t buffer_size = 4096) 
	in
	{
		assert(stream !is null);
		assert(buffer_size > 0);
		assert(stream.readable || stream.writable); //a invalid stream
	}
	body
	{
		m_baseStream = stream;
		m_buffer.length = buffer_size;
	}

	public override bool readable() {
		return m_baseStream.readable;
	}

	public override bool writable() {
		return m_baseStream.writable;
	}

	public override bool seekable() {
		return m_baseStream.seekable;
	}

	public override long length() {
		flush ();
		return m_baseStream.length;
	}

	public override long position() {
		return m_baseStream.position - m_buffer_read_ahead + m_buffer_pos;
	}

	public override void position(long pos)
	{
		if (pos < position && (position - pos <= m_buffer_pos) && m_buffer_reading) 
		{
			m_buffer_pos -= cast(int) (position - pos);
		}
		else if (pos > position && 
					(pos - position < m_buffer_read_ahead - m_buffer_pos) 
					&& m_buffer_reading) 
		{
			m_buffer_pos += cast(int) (pos - position);
		}
		else 
		{
			flush();
			m_baseStream.position = pos;
		}
	}

	public override void close ()
	{
		if (m_buffer !is null) flush();
		m_baseStream.close();
		m_buffer = null;
		m_closed = true;
	}

	public override void flush ()
	{
		if (m_buffer_reading) 
		{
			if (seekable) 
				m_baseStream.position = position;
		}
		else if (m_buffer_pos > 0) 
			m_baseStream.write(m_buffer.ptr, m_buffer_pos);

		m_buffer_read_ahead = 0;
		m_buffer_pos = 0;
	}


	public override long seek (long offset, SeekOrigin origin)
	in {
		assert(seekable);
	}
	body {
		flush ();
		return m_baseStream.seek(offset, origin);
	}


	public override void length (long len)
	in {
		assert(len >= 0);
		assert(seekable && readable);
	}
	body {
		m_baseStream.length = len;
		if (position > len) position = len;
	}

	public override size_t read (void* data, size_t count) 
	in {
		assert(data !is null);
		assert(count > 0);
		assert(readable);
	}
	out(result) {
		assert(result <= count);
	}
	body {
		ubyte* bytes = cast(ubyte*)data;

		if (!m_buffer_reading)
		{
			flush();
			m_buffer_reading = true;
		}

		// m_buffer has not enough space
		if (count <= m_buffer_read_ahead - m_buffer_pos) 
		{
			bytes[0..count] = m_buffer [ m_buffer_pos .. m_buffer_pos + count];

			m_buffer_pos += count;
			if (m_buffer_pos == m_buffer_read_ahead) 
			{
				m_buffer_pos = 0;
				m_buffer_read_ahead = 0;
			}

			return count;
		}

		size_t ret = m_buffer_read_ahead - m_buffer_pos;
		bytes[0 .. ret] = m_buffer [ m_buffer_pos .. m_buffer_pos + ret];
		m_buffer_pos = 0;
		m_buffer_read_ahead = 0;
		size_t offset = ret;
		count -= ret;

		if (count >= m_buffer.length) 
			ret += m_baseStream.read(bytes, count);
		else 
		{
			m_buffer_read_ahead = m_baseStream.read(m_buffer.ptr, m_buffer.length);

			if (count < m_buffer_read_ahead) {
				bytes[offset .. offset + count] = m_buffer[0 .. count];
				m_buffer_pos = count;
				ret += count;
			} else 
			{
				bytes[ offset .. offset + m_buffer_read_ahead] = 
					m_buffer [ 0 .. m_buffer_read_ahead];
				ret += m_buffer_read_ahead;
				m_buffer_read_ahead = 0;
			}
		}

		return ret;
	}


	public override void write (void* data, size_t count)
	in
	{
		assert(data !is null);
		assert(count > 0);
		assert(writable);
	}
	body
	{
		ubyte* bytes = cast(ubyte*)data;
		if (m_buffer_reading) 
		{
			flush();
			m_buffer_reading = false;
		}

		// reordered to avoid possible integer overflow
		if (m_buffer_pos >= m_buffer.length - count) 
		{
			flush ();
			m_baseStream.write (bytes, count);
		} 
		else 
		{
			m_buffer[m_buffer_pos .. m_buffer_pos + count] = bytes[0 .. count];
			m_buffer_pos += count;
		}
	}

	public override bool eos()
	{
		return m_baseStream.eos;
	}
} //class BufferedStream



